package com.neotech.neo4j.rest;

import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import javax.ws.rs.core.MediaType;
import org.apache.log4j.Logger;
import com.neotech.neo4j.Neo4j;
import com.neotech.neo4j.resources.ItemType;
import com.neotech.neo4j.resources.Item;
import com.neotech.neo4j.utils.Constants;
import com.neotech.neo4j.utils.JSONUtil;
import com.neotech.neo4j.utils.Util;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;


/**
 * @author Slobodan
 * 
 * REST implementation for Neo4j
 * Using Jersey Client 
 */

public class Neo4jREST extends Neo4j{

	private static Logger log = Logger.getLogger(Neo4jREST.class.getName());
	private Client client;
	
	public Neo4jREST(String host){
		super(host);
		log.debug(":: Creating Neo4j REST ::");
		client = Client.create();
	}
	
	public Neo4jREST(String host, int port){
		super(host, port);
		log.debug(":: Creating Neo4j REST ::");
		client = Client.create();
	}
	
	public Neo4jREST(String host, int port, String dataEndpoint){
		super(host, port, dataEndpoint);
		log.debug(":: Creating Neo4j REST ::");
		client = Client.create();
	}
	
	public Long createNode() {
		WebResource resource = client.resource(NODE_ENTRY_POINT); 
		ClientResponse response = resource
				.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON).post(ClientResponse.class); 
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		// get id
		Long neoId = JSONUtil.getUriId(responseString);
		// log
		log.debug("node_id=" + neoId);
		return neoId;
	}

	/**
	 * Create new node with node name
	 * */
	public Long createNode(String name) {
		if(name.isEmpty() ){
			log.error("Name cannot be empty");
			return null;
		}
		WebResource resource = client.resource(NODE_ENTRY_POINT); // http://localhost:7474/db/data/node
		ClientResponse response = resource
				.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"" + Constants.NAME + "\":\"" + name + "\"}").post(ClientResponse.class); //
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		// get id
		Long neoId = JSONUtil.getUriId(responseString);
		// log
		log.debug("node_id=" + neoId);
		return neoId;
	}
	
	/**
	 * Create new node with node name and node value.
	 * */
	public Long createNode(String name, String value) {
		if(name.isEmpty() || value == null){
			log.error("Name or value cannot be empty");
			return null;
		}
		WebResource resource = client.resource(NODE_ENTRY_POINT); // http://localhost:7474/db/data/node
		ClientResponse response = resource
				.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"" + Constants.NAME + "\":\"" + name + "\", \"" + Constants.VALUE
						+ "\":\"" + value + "\"}").post(ClientResponse.class); //
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		// get id
		Long neoId = JSONUtil.getUriId(responseString);
		// log
		log.debug("Created node_id=" + neoId);
		return neoId;
	}


	public void deleteItem(Long id, ItemType type) {
		WebResource resource = client.resource(getUrl(id, type));
		ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.delete(ClientResponse.class); 
//		String responseString = response.getEntity(String.class);
//		if(logResponse)
//			log.debug(responseString);
//		Item item = new Item(JSONUtil.getUriId(responseString));
//		item.setProperties(JSONUtil.getPropertyList(responseString));
//		log.debug("Response:" + response);
//		if(response.getStatus() == 200)
			log.debug("Node "+id+" deleted");
	}
	
	 /**
	 * Adding property to node.
	 * if property name has spaces, they are replaced by _
	 * */ 
	public void addPropertyToNode(Long id, String propertyName, String propertyValue) {
		if(propertyName.isEmpty() || propertyValue.isEmpty()){
			log.error("Property name or value cannot be empty");
			return;
		}
		
		String[] split = propertyName.split(" ");
		StringBuilder property = new StringBuilder();
		for (int i = 0; i < split.length; i++) {
			if(i != 0){
				property.append("_");
			}
				property.append(split[i]);
		}
		// log
		log.debug("Adding property to node: " + id + " ["
				+ property + ":" + propertyValue + "]");
		final String PROPERTY_URI = NODE_ENTRY_POINT + id + "/properties/"+property;
		WebResource resource = client.resource(PROPERTY_URI); // http://localhost:7474/db/data/node/{node_id}/properties/{property_name}
		ClientResponse res = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON_TYPE)
				.entity("\"" + propertyValue + "\"").put(ClientResponse.class);
		log.debug("Status:" + res.getStatus());
	}

	 /**
	 * Adding relationship between two nodes.
	 * */
	public Long addRelationship(Long nodeFrom, Long nodeTo,
			String relName) {
		String FROM_URI = NODE_ENTRY_POINT + nodeFrom + "/relationships";
		String NODE_TO = NODE_ENTRY_POINT + nodeTo;

		// log
		log.debug("Adding relationship: " + NODE_ENTRY_POINT
				+ nodeFrom + "--" + relName + "-->" + NODE_TO);

		String relationshipJson = JSONUtil.generateJsonRelationship(NODE_TO,
				relName, new String[] {});

		WebResource resource = client.resource(FROM_URI);
		ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON).entity(relationshipJson)
				.post(ClientResponse.class); // POST JSON to the relationships
												// URI
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		return JSONUtil.getUriId(responseString);
	}

	public void addPropertyToRelationship(Long id,
			String propertyName, String propertyValue) {
		if(propertyName.isEmpty() || propertyValue.isEmpty()){
			log.error("Property name or value cannot be empty");
			return;
		}
		
		String[] split = propertyName.split(" ");
		StringBuilder property = new StringBuilder();
		for (int i = 0; i < split.length; i++) {
			if(i != 0){
				property.append("_");
			}
				property.append(split[i]);
		}
		
		// log
		log.debug("Adding property to relationhip: " + id + " ["
				+ property + ":" + propertyValue + "]");
		final String PROPERTY_URI = RELATIONSHIP_ENTRY_POINT + id
				+ "/properties/" + property;
		WebResource resource = client.resource(PROPERTY_URI); 
		ClientResponse res = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.entity("\"" + propertyValue + "\"").put(ClientResponse.class);
		log.debug("Status:" + res.getStatus());
	}

	
	public void indexNode(Long id, String indexName, String indexKey, String indexValue){
		addToIndex(id, indexName, indexKey, indexValue, ItemType.NODE);
	}
		
	public void addToIndex(Long id, String indexName, String indexKey, String indexValue, ItemType type) {
		String indexUri = INDEX_ENTRY_POINT + type.toString() + "/"+indexName
				+ "/" + Util.encode(indexKey) + "/" + Util.encode(indexValue);
		if(indexName.isEmpty() || indexValue.isEmpty()){
			log.error("Index name or value cannot be empty");
			return;
		}
		log.debug("Indexing node: "+NODE_ENTRY_POINT+id );
		WebResource resource = client.resource(indexUri);
		ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.entity("\"" + NODE_ENTRY_POINT+id + "\"")
				.post(ClientResponse.class); 
		log.debug("Response:" + response);
	}

	public Item getItemById(Long id, ItemType type) {
		WebResource resource = client.resource(getUrl(id, type));
		ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.get(ClientResponse.class); 
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		Item item = new Item(JSONUtil.getUriId(responseString));
		item.setProperties(JSONUtil.getPropertyList(responseString));
		log.debug("Response:" + response);
		return item;
	}
	
	public String getPropertyValue(Long id, String propertyName, ItemType type) {
		Item item = getItemById(id, type);
		return item.getProperty(propertyName);
	}

	public ArrayList<Item> queryIndex(String indexName,
			String propertyName, String propertyValue, ItemType type) {
		if(propertyName.isEmpty() || propertyValue.isEmpty()){
			log.error("Property name or value cannot be empty");
			return null;
		}
		String indexUri = INDEX_ENTRY_POINT + type.toString() + "/"+indexName
				+ "/" +  Util.encode(propertyName)+ "/" + Util.encode(propertyValue);
		log.debug("Query index: "+indexUri);
		WebResource resource = client.resource(indexUri);
		ClientResponse response = resource.accept(MediaType.APPLICATION_JSON)
				.type(MediaType.APPLICATION_JSON)
				.get(ClientResponse.class); 
		String responseString = response.getEntity(String.class);
		if(logResponse)
			log.debug(responseString);
		return JSONUtil.parseItemsFromJson(responseString);
	}

	public Long getRootNode() {
		return 0L;
	}

}
